﻿using CommonModel.Util;
using GXRTBTC.PublicClassLibrary.Model;
using Microsoft.Extensions.Logging;
using NewLife;
using NewLife.Data;
using NewLife.Http;
using NewLife.Messaging;
using NewLife.Remoting;
using Newtonsoft.Json;
using NLog.Fluent;
using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Net.Mime;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using WebSocketMessageType = System.Net.WebSockets.WebSocketMessageType;

namespace GXRTBTC.PublicClassLibrary.Util
{
    /// <summary>
    /// 请求类
    /// </summary>
    public class HttpClientUtil
    {
        /// <summary>
        /// 日志
        /// </summary>
        public ILogger mLogger = null;
        /// <summary>
        /// WebSocket心跳发送间隔
        /// </summary>
        public int heartInterval { get; set; } = 3000;
        /// <summary>
        /// http请求池
        /// </summary>
        private HttpClientPool httpClientPool { get; set; }
        /// <summary>
        /// https请求池
        /// </summary>
        private HttpClientPool httpsClientPool { get; set; }
        /// <summary>
        /// WebSocket连接
        /// </summary>
        private Dictionary<string, ClientWebSocket> clientWebSockets { get; set; } = new Dictionary<string, ClientWebSocket>();
        /// <summary>
        /// WebSocket是否发送心跳
        /// </summary>
        private Dictionary<string, bool> wsHeartbeatEnables { get; set; } = new Dictionary<string, bool>();
        /// <summary>
        /// 当前连接是否在接收数据
        /// </summary>
        private Dictionary<string, bool> clientWebSocketListen { get; set; } = new Dictionary<string, bool>();
        private bool HeartbeatRunning { get; set; } = false;
        /// <summary>
        /// token
        /// </summary>
        private string accessToken { get; set; }
        /// <summary>
        /// token有效期，单位：秒
        /// 默认-1，代表长期
        /// </summary>
        private int duration { get; set; } = -1;
        private DateTime tokenTime { get; set; } = DateTime.Now;

        /// <summary>
        /// 构造
        /// </summary>
        public HttpClientUtil(ILogger mLogger) 
        { 
            this.mLogger = mLogger;
        }
        /// <summary>
        /// 携带token和有效期
        /// </summary>
        /// <param name="accessToken"></param>
        /// <param name=""></param>
        public HttpClientUtil(string accessToken, int duration = -1)
        {
            SetToken(accessToken, duration);
        }
        /// <summary>
        /// 设置token和过期时间
        /// </summary>
        /// <param name="accessToken"></param>
        /// <param name="duration"></param>
        public void SetToken(string accessToken, int duration = -1)
        {
            this.accessToken = accessToken;
            this.duration = duration;
            tokenTime = DateTime.Now;
            httpClientPool = null;
            httpsClientPool = null;
        }
        /// <summary>
        /// 获取token是否有效
        /// </summary>
        /// <returns></returns>
        public bool GetTokenValidity()
        {
            //提前5秒判断不在有效期，便于有时间重新获取
            if (accessToken.IsNullOrEmpty() || duration == -1 || (DateTime.Now - tokenTime).TotalSeconds < (duration - 5))
            {
                return true;
            }

            return false;
        }
        #region http
        /// <summary>
        /// 获取HttpClient
        /// </summary>
        /// <returns></returns>
        private HttpClientPool GetHttpPool()
        {
            if (httpClientPool == null)
            {
                //http请求池
                httpClientPool = new HttpClientPool
                {
                    Name = "httpClientPool",
                    https = false,
                    accessToken = accessToken,
                    timeout = 5,
                    Min = 0,  //最小poolNum个连接
                              //Max = maxPoolNum * 2,   //最大maxPoolNum个连接
                    Max = 0,   //最大个数。默认100，0表示无上限
                    IdleTime = 10,  //空闲时间20秒，超过该空闲时间的连接将被销毁释放，直到留下最后Min个
                    AllIdleTime = 60   //完全空闲时间120秒，把最后Min个连接都干掉
                };
            }

            return httpClientPool;
        }

        /// <summary>
        /// 获取HttpClient
        /// </summary>
        /// <returns></returns>
        private HttpClientPool GetHttpsPool()
        {
            if (httpsClientPool == null)
            {
                //http请求池
                httpsClientPool = new HttpClientPool
                {
                    Name = "httpClientPool",
                    https = true,
                    accessToken = accessToken,
                    timeout = 5,
                    Min = 0,  //最小poolNum个连接
                              //Max = maxPoolNum * 2,   //最大maxPoolNum个连接
                    Max = 0,   //最大个数。默认100，0表示无上限
                    IdleTime = 10,  //空闲时间20秒，超过该空闲时间的连接将被销毁释放，直到留下最后Min个
                    AllIdleTime = 60   //完全空闲时间120秒，把最后Min个连接都干掉
                };
            }

            return httpsClientPool;
        }

        /// <summary>
        /// 获取HttpClient池
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public HttpClientPool GetClientPool(string url)
        {
            return url.ToUpper().StartsWith("HTTPS") ? GetHttpsPool() : GetHttpPool();
        }

        /// <summary>
        /// get请求
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        private async Task<string> GetAsync(string url)
        {
            string result = "";
            HttpClient client = GetClientPool(url).Get();
            try
            {
                //每一个api资源都有一个对应的网关地址
                HttpResponseMessage response = await client.GetAsync(url);
                if (response.IsSuccessStatusCode)
                {
                    result = await response.Content.ReadAsStringAsync();
                }
            }
            catch (Exception e)
            {
                mLogger.LogError(e.Message);
            }
            finally
            {
                if (client != null)
                {
                    GetClientPool(url).Put(client);
                }
            }

            return result;
        }

        /// <summary>
        /// get请求
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public string Get(string url)
        {
            return ConvertObj<string>(GetAsync(url).Result);
        }

        /// <summary>
        /// get请求
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="url"></param>
        /// <returns></returns>
        public T Get<T>(string url)
        {
            return ConvertObj<T>(GetAsync(url).Result);
        }

        /// <summary>
        /// post请求
        /// </summary>
        /// <param name="url"></param>
        /// <param name="data"></param>
        /// <param name="contentType"></param>
        /// <returns></returns>
        private async Task<string> PostAsync(string url, string data, string contentType = "application/json")
        {
            string result = "";
            HttpClient client = GetClientPool(url).Get();
            try
            {
                //在使用编码方法（Encoding.GetEncoding("GB2312")）之前，对编码进行注册
                //Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
                //var content = new StringContent(data, Encoding.GetEncoding(TransForm.encode), contentType);
                //var content = new StringContent(data, Encoding.GetEncoding("utf-8"), contentType);

                HttpResponseMessage response;
                //针对application/x-www-form-urlencoded的处理
                if (contentType.ToLower().Contains("x-www-form-urlencoded"))
                {
                    FormUrlEncodedContent content = new FormUrlEncodedContent(JsonConvert.DeserializeObject<Dictionary<string, string>>(data));
                    response = await client.PostAsync(url, content);
                }
                else
                {
                    //默认json
                    StringContent content = new StringContent(data, Encoding.UTF8, contentType);
                    response = await client.PostAsync(url, content);
                }

                if (response.IsSuccessStatusCode)
                {
                    result = await response.Content.ReadAsStringAsync();
                }
                else
                {
                    mLogger.LogError("Post error, url=" + url + " StatusCode=" + response.StatusCode);
                }
            }
            catch (Exception e)
            {
                mLogger.LogError(e.Message);
            }
            finally
            {
                if (client != null)
                {
                    GetClientPool(url).Put(client);
                }
            }
            return result;
        }

        /// <summary>
        /// post请求
        /// </summary>
        /// <param name="url"></param>
        /// <param name="data"></param>
        /// <param name="contentType"></param>
        /// <returns></returns>
        public string Post(string url, string data, string contentType = "application/json")
        {
            return ConvertObj<string>(PostAsync(url, data, contentType).Result);
        }

        /// <summary>
        /// post请求
        /// </summary>
        /// <param name="url"></param>
        /// <param name="data"></param>
        /// <param name="contentType"></param>
        /// <returns></returns>
        public string Post(string url, Object data, string contentType = "application/json")
        {
            return ConvertObj<string>(PostAsync(url, JsonConvert.SerializeObject(data), contentType).Result);
        }

        /// <summary>
        /// post请求
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="url"></param>
        /// <param name="data"></param>
        /// <param name="contentType"></param>
        /// <returns></returns>
        public T Post<T>(string url, string data, string contentType = "application/json")
        {
            return ConvertObj<T>(PostAsync(url, data, contentType).Result);
        }

        /// <summary>
        /// post请求
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="url"></param>
        /// <param name="data"></param>
        /// <param name="contentType"></param>
        /// <returns></returns>
        public T Post<T>(string url, Object data, string contentType = "application/json")
        {
            return ConvertObj<T>(PostAsync(url, JsonConvert.SerializeObject(data), contentType).Result);
        }

        /// <summary>
        /// post请求
        /// 带封装
        /// </summary>
        /// <param name="url"></param>
        /// <param name="data"></param>
        /// <param name="command"></param>
        /// <param name="contentType"></param>
        /// <returns></returns>
        public string Post(string url, Object data, string command, string contentType = "application/json")
        {
            return ConvertObj<string>(PostAsync(url, JsonConvert.SerializeObject(QuickClass.PackData(data, command)), contentType).Result);
        }

        /// <summary>
        /// post请求
        /// 带封装
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="url"></param>
        /// <param name="data"></param>
        /// <param name="command"></param>
        /// <param name="contentType"></param>
        /// <returns></returns>
        public T Post<T>(string url, Object data, string command, string contentType = "application/json")
        {
            return ConvertObj<T>(PostAsync(url, JsonConvert.SerializeObject(QuickClass.PackData(data, command)), contentType).Result);
        }
        #endregion

        #region WebSocket
        /// <summary>
        /// WebSocket心跳自动发送
        /// </summary>
        private void HeartbeatWebSocketClient()
        {
            Stopwatch sw;
            while (true)
            {
                sw = Stopwatch.StartNew();
                try
                {
                    if (clientWebSockets.Count > 0)
                    {
                        foreach (var key in clientWebSockets.Keys)
                        {
                            if (clientWebSockets[key].State == WebSocketState.Open && wsHeartbeatEnables.ContainsKey(key) && wsHeartbeatEnables[key])
                            {
                                WebApiDataReqWS webApiDataReqWS = QuickClass.PackWSData("", "default", WebApiTypeCodeWS.heartbeat.ToString());
                                //WebSocketSend(clientWebSockets[key], webApiDataReqWS);
                                WebSocketSend(clientWebSockets[key], JsonConvert.SerializeObject(webApiDataReqWS));
                            }
                        }
                    }
                }
                catch
                { }
                finally
                {
                    int gap = (int)sw.ElapsedMilliseconds;
                    Thread.Sleep(gap >= heartInterval ? 1 : (heartInterval - gap));
                }
            }
        }

        /// <summary>
        /// 获取ClientWebSocket
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        private async Task<ClientWebSocket> GetWebSocketClient(string url, bool heartbeatEnable = false)
        {
            try
            {
                if (!clientWebSockets.ContainsKey(url))
                {
                    ClientWebSocket client = new ClientWebSocket();
                    await client.ConnectAsync(new Uri(url), CancellationToken.None);
                    clientWebSockets.TryAdd(url, client);
                    wsHeartbeatEnables.TryAdd(url, heartbeatEnable);
                    clientWebSocketListen.TryAdd(url, false);
                    mLogger.LogInformation("打开WebSocket");
                }
                else if (clientWebSockets[url].State == WebSocketState.Closed || clientWebSockets[url].State == WebSocketState.Aborted)
                {
                    clientWebSockets[url] = new ClientWebSocket();
                    wsHeartbeatEnables[url] = heartbeatEnable;
                    clientWebSocketListen[url] = false;
                    await clientWebSockets[url].ConnectAsync(new Uri(url), CancellationToken.None);
                    mLogger.LogInformation("重连WebSocket");
                }

                //启动心跳发送线程
                if (!HeartbeatRunning)
                {
                    HeartbeatRunning = true;
                    new Thread(HeartbeatWebSocketClient).Start();
                }
                return clientWebSockets[url];
            }
            catch (Exception e)
            {
                mLogger.LogError(url + " error:" + e.Message);
                return null;
            }
        }

        /// <summary>
        /// 获取ClientWebSocket并接收数据
        /// </summary>
        /// <param name="url"></param>
        /// <param name="onReceive"></param>
        /// <returns></returns>
        public async Task<ClientWebSocket> GetWebSocketClientAndRcv(string url, Action<Object> onReceive = null, bool heartbeatEnable = false)
        {
            try
            {
                ClientWebSocket clientWebSocket = await GetWebSocketClient(url, heartbeatEnable);
                if (clientWebSocketListen.ContainsKey(url))
                {
                    if (!clientWebSocketListen[url])
                    { 
                        OnWebSocketReceive(clientWebSocket, onReceive);
                    }
                    clientWebSocketListen[url] = true;
                    return clientWebSockets[url];
                }
                else
                { 
                    return null;
                }
            }
            catch (Exception e)
            {
                mLogger.LogError(e.Message);
                return null;
            }
        }

        /// <summary>
        /// 发送
        /// </summary>
        /// <param name="clientWebSocket"></param>
        /// <param name="message"></param>
        public async void WebSocketSend(ClientWebSocket clientWebSocket, string message)
        {
            try
            {
                if (clientWebSocket == null)
                {
                    mLogger.LogError("clientWebSocket为null");
                    return;
                }
                byte[] buffer = Encoding.UTF8.GetBytes(message);
                await clientWebSocket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, CancellationToken.None);
            }
            catch (Exception e)
            {
                mLogger.LogError(e.Message);
            }
        }

        /// <summary>
        /// 发送
        /// </summary>
        /// <param name="clientWebSocket"></param>
        /// <param name="message"></param>
        /// <param name="command"></param>
        public void WebSocketSend(ClientWebSocket clientWebSocket, Object message, string command = "default", string type = "data")
        {
            WebSocketSend(clientWebSocket, JsonConvert.SerializeObject(QuickClass.PackWSData(message, command, type)));
        }

        /// <summary>
        /// 发送
        /// </summary>
        /// <param name="url"></param>
        /// <param name="message"></param>
        public void WebSocketSend(string url, string message)
        {
            ClientWebSocket clientWebSocket = GetWebSocketClient(url).Result;
            WebSocketSend(clientWebSocket, message);
        }

        /// <summary>
        /// 关闭
        /// </summary>
        /// <param name="clientWebSocket"></param>
        /// <param name="message"></param>
        public async void WebSocketClose(ClientWebSocket clientWebSocket)
        {
            try
            {
                if (clientWebSocket == null)
                {
                    mLogger.LogError("clientWebSocket为null");
                    return;
                }
                if (clientWebSocket.State == WebSocketState.Open || clientWebSocket.State == WebSocketState.CloseSent || clientWebSocket.State == WebSocketState.CloseReceived)
                {
                    await clientWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
                }
                mLogger.LogInformation("关闭WebSocket");
            }
            catch (Exception e)
            {
                mLogger.LogError(e.Message);
            }
        }

        /// <summary>
        /// 关闭
        /// </summary>
        /// <param name="clientWebSocket"></param>
        /// <param name="message"></param>
        public void WebSocketClose(string url)
        {
            ClientWebSocket clientWebSocket = GetWebSocketClient(url).Result;
            WebSocketClose(clientWebSocket);
        }

        /// <summary>
        /// 接收数据
        /// </summary>
        /// <param name="clientWebSocket"></param>
        /// <param name="onReceive"></param>
        private async void OnWebSocketReceive(ClientWebSocket clientWebSocket, Action<Object> onReceive)
        {
            try
            {
                if (clientWebSocket == null)
                {
                    mLogger.LogError("clientWebSocket为null");
                    return;
                }

                var buffer = new byte[1024 * 4];
                WebSocketReceiveResult receiveResult = await clientWebSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
                while (!receiveResult.CloseStatus.HasValue)
                {
                    string data = Encoding.UTF8.GetString(buffer, 0, receiveResult.Count);
                    try
                    {
                        WebApiDataResWS webApiDataResWS = JsonConvert.DeserializeObject<WebApiDataResWS>(data);
                        TaskCompletionSource<Object> tcs = TcsTimeoutHelper.tcsManager.GetTcs(webApiDataResWS.RequestId);
                        if (tcs != null)
                        {
                            tcs.TrySetResult(webApiDataResWS);
                            TcsTimeoutHelper.tcsManager.RemoveTcs(webApiDataResWS.RequestId);
                        }
                        onReceive?.Invoke(webApiDataResWS);
                    }
                    catch (Exception e)
                    {
                        try
                        {
                            onReceive?.Invoke(data);
                        }
                        catch { }
                        //避免回调异常时影响数据接收，添加try
                        mLogger.LogError(e.Message);
                    }
                    //receiveResult = await clientWebSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
                    if (clientWebSocket.State == WebSocketState.Open)
                    {
                        receiveResult = await clientWebSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
                    }
                    else
                    {
                        break;
                    }
                }

                if (receiveResult.CloseStatus != null && (clientWebSocket.State == WebSocketState.Open || clientWebSocket.State == WebSocketState.CloseSent || clientWebSocket.State == WebSocketState.CloseReceived))
                {
                    //await clientWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
                    await clientWebSocket.CloseAsync(receiveResult.CloseStatus.Value, receiveResult.CloseStatusDescription, CancellationToken.None);
                    CommonUtil.LogInformation("关闭WebSocket");
                }
            }
            catch (Exception e)
            {
                mLogger.LogError(e.Message);
            }
        }
        #endregion

        #region 其它
        /// <summary>
        /// 发送命令到采集
        /// 同时兼容http和websocket
        /// 返回规范数据
        /// </summary>
        /// <param name="data"></param>
        /// <param name="command"></param>
        /// <param name="url"></param>
        /// <returns></returns>
        public async Task<IWebApiData> SendTo(Object data, string command, string url)
        {
            IWebApiData webApiData;
            try
            {
                if (url.ToUpper().StartsWith("WS"))
                {
                    ClientWebSocket clientWebSocket = await GetWebSocketClientAndRcv(url, null, true);
                    //发送并获得一个任务
                    TaskCompletionSource<Object> tcs = clientWebSocket.Send(data, command);

                    int tm = 5;
#if DEBUG
                    //方便测试
                    tm = 30;
#endif
                    //等待任务结束
                    var result = TcsTimeoutHelper.WaitWithTimeout(tcs, TimeSpan.FromSeconds(tm)).Result;
                    if (result is WebApiDataResWS)
                    {
                        webApiData = (WebApiDataResWS)result;
                    }
                    else
                    {
                        webApiData = ConvertObj<WebApiDataResWS>(result.ToString());
                    }
                }
                else
                {
                    string result = Post(url, data, command);
                    webApiData = ConvertObj<WebApiDataRes>(result);
                }
            }
            catch (Exception e)
            {
                webApiData = new WebApiDataRes() { Code = WebApiStatusCode.code_5503, Message = "执行异常，" + e.Message };
                //mLogger.LogError(e.ToString());
                mLogger.LogError(e.Message);
            }

            return webApiData;
        }

        /// <summary>
        /// 字符串转对象
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value"></param>
        /// <returns></returns>
        private T ConvertObj<T>(string value)
        {
            try
            {
                if (typeof(T).Name.Equals("String"))
                {
                    return string.IsNullOrEmpty(value) ? default(T) : (T)Convert.ChangeType(value, typeof(T));
                }
                else if (typeof(T).Name.Equals("DateTime"))
                {
                    return string.IsNullOrEmpty(value) ? default(T) : (T)Convert.ChangeType(value.ToString().Replace("\"", ""), typeof(T));
                }
                else if (string.IsNullOrEmpty(value))
                {
                    //强制转string
                    if (typeof(T).Name.Equals("Object"))
                    {
                        return (T)Convert.ChangeType(value, typeof(String));
                    }
                    return default(T);
                }
                else
                {
                    return JsonConvert.DeserializeObject<T>(value);
                }
            }
            catch
            {
                try
                {
                    return (T)Convert.ChangeType(value, typeof(T));
                }
                catch
                {
                    return default(T);
                }
            }
        }
        #endregion
    }

    /// <summary>
    /// WebSocket自定义
    /// </summary>
    public static class QuickClass
    {
        /// <summary>
        /// 自定义发送
        /// </summary>
        /// <param name="clientWebSocket"></param>
        /// <param name="message"></param>
        public static async void Send(this ClientWebSocket clientWebSocket, string message)
        {
            try
            {
                if (clientWebSocket == null)
                {
                    CommonUtil.LogError("clientWebSocket为null，无法发送数据");
                    return;
                }
                byte[] buffer = Encoding.UTF8.GetBytes(message);
                await clientWebSocket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, CancellationToken.None);
            }
            catch (Exception e)
            {
                CommonUtil.LogError(e.Message);
            }
        }

        /// <summary>
        /// 自定义发送
        /// </summary>
        /// <param name="clientWebSocket"></param>
        /// <param name="message"></param>
        /// <param name="command"></param>
        public static TaskCompletionSource<Object> Send(this ClientWebSocket clientWebSocket, Object message, string command, string type = "data")
        {
            WebApiDataReqWS webApiDataReqWS = PackWSData(message, command, type);

            CommonUtil.LogInformation(JsonConvert.SerializeObject(webApiDataReqWS));

            clientWebSocket.Send(JsonConvert.SerializeObject(webApiDataReqWS));
            return TcsTimeoutHelper.tcsManager.SetTcs(webApiDataReqWS.RequestId);
        }

        /// <summary>
        /// 请求数据
        /// </summary>
        /// <param name="data"></param>
        /// <param name="command"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        public static WebApiDataReqWS PackWSData(Object data, string command, string type = "data")
        {
            WebApiDataReqWS webApiDataReqWS = new WebApiDataReqWS();
            webApiDataReqWS.Command = command;
            webApiDataReqWS.Data = data;
            webApiDataReqWS.Type = type;
            return webApiDataReqWS;
        }

        /// <summary>
        /// 请求数据
        /// </summary>
        /// <param name="data"></param>
        /// <param name="command"></param>
        /// <returns></returns>
        public static WebApiDataReq PackData(Object data, string command)
        {
            WebApiDataReq webApiDataReq = new WebApiDataReq();
            webApiDataReq.Command = command;
            webApiDataReq.Data = data;
            return webApiDataReq;
        }

        /// <summary>
        /// 自定义关闭
        /// </summary>
        /// <param name="clientWebSocket"></param>
        public static async void Close(this ClientWebSocket clientWebSocket)
        {
            try
            {
                if (clientWebSocket == null)
                {
                    CommonUtil.LogError("clientWebSocket为null");
                    return;
                }
                if (clientWebSocket.State == WebSocketState.Open || clientWebSocket.State == WebSocketState.CloseSent || clientWebSocket.State == WebSocketState.CloseReceived)
                {
                    await clientWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
                }
                CommonUtil.LogInformation("关闭WebSocket");
            }
            catch (Exception e)
            {
                CommonUtil.LogError(e.Message);
            }
        }
    }
}
